'''
Created on 2011-06-02

@author: Earl Bell
'''
import serial
from time import sleep, time
import threading
#import sys
import OSEKitsDefine
import tkMessageBox

class OSESerialComs(object):
    '''
    classdocs
    '''
    __BAUDRATES_LIST = [9600,19200,38400, 57600, 115200]
    _output_com = serial.Serial()
    _input_com = serial.Serial()
    _received_command_packets = []
    _SERIALPORT = '//dev/ttyACM0'
    _BAUDRATE = 115200
    _BYTESIZE = serial.EIGHTBITS
    _PARITY = serial.PARITY_NONE
    _STOPBITS = serial.STOPBITS_ONE
    _FLOWCONTROL = False
    _input_serial_monitor_thread = threading.Thread()
    _input_serial_monitor_thread_stop_flag = False
    _received_measurements = []
   
    def isMeasurementValueReady(self):
        '''
        Function: isMeasurementValueReady
        
        Description:
            Checks _received_measurements for new measurements.
            
        Returns:
            boolean:  True  if length of _received_measurements is greater than 0
                      False otherwise
        '''
        if len(self._received_measurements) > 0:
            return True
        else:
            return False
   
    def getMeasurementValue(self):
        '''
        Function: getMeasurementValue
        
        Description:
            Returns the oldest measurement value received in _received_measurements
            
        Returns:
            string
        '''
        return self._received_measurements.pop(0)
   
    '''
    Accessors and Mutators for serial port configuration
    '''
    
    def setPort(self, newPort):
        self._SERIALPORT = newPort
        return 0
   
    def getPort(self):
        getport = self._SERIALPORT
        return getport
   
    def setBaudrate(self, newBaudrate):
        if newBaudrate not in self.__BAUDRATES_LIST:
            return -1
        else:
            self._BAUDRATE = newBaudrate
        return 0
   
    def getBaudrate(self):
        getbaud = self._BAUDRATE
        return getbaud
   
    def setBytesize(self, newBytesize):
        if newBytesize == 5:
            self._BYTESIZE = serial.FIVEBITS
        elif newBytesize == 6:
            self._BYTESIZE = serial.SIXBITS
        elif newBytesize == 7:
            self._BYTESIZE = serial.SEVENBITS
        elif newBytesize == 8:
            self._BYTESIZE = serial.EIGHTBITS
        else:
            return -1
       
        return 0
   
    def getBytesize(self):
        if self._BYTESIZE == serial.FIVEBITS:
            return 5
        elif self._BYTESIZE == serial.SIXBITS:
            return 6
        elif self._BYTESIZE == serial.SEVENBITS:
            return 7
        elif self._BYTESIZE == serial.EIGHTBITS:
            return 7
       
    def setParity(self, newParity):
        if newParity == "even":
            self._PARITY = serial.PARITY_EVEN
        elif newParity == "odd":
            self._PARITY = serial.PARITY_ODD
        elif newParity == "none":
            self._PARITY = serial.PARITY_NONE
        else:
            return -1
       
        return 0
   
    def getParity(self):
        if self._PARITY == serial.PARITY_EVEN:
            return "even"
        elif self._PARITY == serial.PARITY_ODD:
            return "odd"
        elif self._PARITY == serial.PARITY_NONE:
            return "none"
   
    def setStopbits(self, newStopbits):
        if newStopbits == '1':
            self._STOPBITS = serial.STOPBITS_ONE
        elif newStopbits == '1.5':
            self._STOPBITS = serial.STOPBITS_ONE_POINT_FIVE
        elif newStopbits == '2':
            self._STOPBITS = serial.STOPBITS_TWO
        else:
            return -1
       
        return 0
   
    def getStopbits(self):
        if self._STOPBITS == serial.STOPBITS_ONE:
            return '1'
        elif self._STOPBITS == serial.STOPBITS_ONE_POINT_FIVE:
            return '1.5'
        elif self._STOPBITS == serial.STOPBITS_TWO:
            return '2'
       
     
    def setFlowControl(self, newFlowcontrol):
        self._FLOWCONTROL = newFlowcontrol
        return 0
   
    def getFlowControl(self):
        getFlow = self._FLOWCONTROL
        return getFlow
   
    def __init__(self):
        '''
        Constructor
        port='/dev/ttyACM0',
        baudrate=115200,
        bytesize=EIGHTBITS,
        parity=PARITY_NONE,
        stopbits=STOPBITS_ONE,
        timeout=None,
        xonxoff=False,
        rtscts=False,
        writeTimeout=None,
        dsrdtr=False,
        interCharTimeout=None
        '''
           
        return

    def initializePort(self):
        '''
        Function: initializePort
        Description:
            Opens serial port _output_com for writing to the COM port.  Starts _input_serial_monitor_thread which monitors for incoming 
            serial data.
        Returns:
            string: "success" on success startup
                    "<some error>" on non-successfull startup
        '''        
        self._input_serial_monitor_thread_stop_flag = False
        #open output COM Port
        try:
            self._output_com = serial.Serial(port=self._SERIALPORT, baudrate=self._BAUDRATE, bytesize=self._BYTESIZE, parity=self._PARITY, stopbits=self._STOPBITS, timeout=None, xonxoff=self._FLOWCONTROL, rtscts=False, writeTimeout=None, dsrdtr=False, interCharTimeout=None)
        except serial.SerialException, e:
            return e
       
        try:
            self._input_serial_monitor_thread = threading.Thread(target=self.serialReader).start()
        except Exception, e:
            return e
           
        sleep(1)
       
        try:
            self._output_com.open()
        except serial.SerialException, e:
            return e
           
        return "success"
   
    def closePort(self):
        '''
        Function: closePort
        
        Description:
            Closes output COM port and stops _input_serial_monitor_thread
            
        Returns:
            string   "success" if no errors
                     "<error message>" otherwise
        '''
        self._input_serial_monitor_thread_stop_flag = True
       
        while (self._input_serial_monitor_thread <> None) and (self._input_serial_monitor_thread.isAlive()):
            wait = 1
       
        try:
            self._output_com.close()
        except serial.SerialException, e:
            return str(e)
       
        return "success"
   
    def serialReader(self):
        '''
        Function: serialReader
        Description:
            serial reader listens on the serial port for incoming data.  When incoming data is received,
            one byte at a time is stored into a list that is shared with the main thread.
       
            Header packet definition:
             *         bits 0, 1, 2: Packet type
             *             0-2    Defines the type of packet being sent
             *            000 = NULL PACKET
             *            001 = CONFIG PACKET
             *            010 = MEASUREMENT PACKET
             *            011 = CONTROL PACKET
             *            100 = ACKNOWLEDGE PACKET
             *            101 - 111 = NULL PACKET
             *         bits 3, 4, 5: Packet size in binary.
             *             Minimum 1 byte
             *             Max 7 bytes
             *         bits 6, 7:
             *           in control packet:
             *             00 = Stop Measurement
             *             11 = Start Measurement
             *           in acknowledge packet:
             *            00 = error
             *            01 = done
             *            11 = OK
             *           in config packet:
             *            00 = thermocouple mode
             *            01 = encoder mode
             *            10 = photogate mode            
           
        Returns: no return value
        
        '''
        connect = 0
        while connect <> 1:
            
            try:
                _input_com = serial.Serial(port=self._SERIALPORT, baudrate=self._BAUDRATE, bytesize=self._BYTESIZE, parity=self._PARITY, stopbits=self._STOPBITS, timeout=5, xonxoff=self._FLOWCONTROL, rtscts=False, writeTimeout=None, dsrdtr=False, interCharTimeout=5)
                _input_com.open()
                connect = 1
               
            except serial.SerialException, e:
               
                return str(e)
           
            if self._input_serial_monitor_thread_stop_flag == True:
                return
            
        _input_com.flushInput()
       
        while(self._input_serial_monitor_thread_stop_flag == False):
            try:
                read = _input_com.read(1)
            except Exception, e:
                tkMessageBox.showerror("Serial Read Error", "Unable to read from Serial Port.\nDisconnect then re-connect to micro-controller.\n")
                self._input_serial_monitor_thread_stop_flag = True
            if len(read) > 0:
                print "received:",ord(read)
                #check if packet is a measurement packet
                packettype = ord(read) & OSEKitsDefine.PACKET_TYPE_MASK
                
                if packettype == OSEKitsDefine.MEASUREMENT_PACKET:
               
                    #Get the length of the packet in bytes from the header
                    packetbytes = ((ord(read) & OSEKitsDefine.PACKET_LENGTH_MASK) >> 3) - 1
                    try:
                        #read the correct number of bytes from the serial port
                        read = _input_com.read(packetbytes)
                        #store the measurement in the _received_measurements list
                        self._received_measurements.append(read)
                    except:
                        wait = 1
                   
                    packettype = OSEKitsDefine.NULL_PACKET
                elif packettype == OSEKitsDefine.ACKNOWLEDGE_PACKET:
                    #Get the length of the packet in bytes from the header
                   
                    packetbytes = ((ord(read) & OSEKitsDefine.PACKET_LENGTH_MASK) >> 3) - 1
                   
                    self._received_command_packets.append(ord(read))
                   
                    #read the correct number of bytes from the serial port
                    if packetbytes > 0:
                        try:
                            read = _input_com.read(packetbytes)
                            #store the measurement in the _received_measurements list
                            self._received_command_packets.append(ord(read))
                        except:
                            wait = 1
                    packettype = OSEKitsDefine.NULL_PACKET
                else:
                    #tkMessageBox.showerror("Communication Error", "BAD PACKET:" + str(ord(read)))
                    wait = 1
            else:
                read = None
       
        return  
           
           
    def sendConfigPacket(self, sensorMode = OSEKitsDefine.THERMOCOUPLE_MODE, configdata = []):
        '''
        Function: sendConfigPacket
       
        Description:
            Sends a config packet through the serial connection. Waits for acknowledgement packet.
        Parameters:
            sensorMode:  a two bit value indicating which sensor is to be used by the micro-controller
            configdata:  a list of single byte configuration values
        Returns:
            string  value from OSESerialComs._awaitAcknowledge
        '''
       
        if not self._output_com.isOpen():
            return "SERIAL ERROR: Serial connection not open"
       
       
        configpacket = OSEKitsDefine.CONFIG_PACKET | ((len(configdata)+1)<<3) | (sensorMode<<6)
        try:
            self._output_com.write(chr(configpacket))
            for value in configdata:
                self._output_com.write(chr(value))

        except:
            return "Serial Communication Error.\nCould not send config packet."
        
       
           
        return self._await_acknowledge()    
   
    def _await_acknowledge(self):
        '''
        Function: _await_acknowledge
        
        Description:
            Monitors _received_command_packets and waits for an acknowledge packet to appear.
            Will time out after reaching timeout value in OSEKitsDefine.py
            
        Returns:
            string   "success"  upon receiving valid "OK" acknowledge packet
                     "<error message>" otherwise
        '''
        timeout_start = time()
        timeout = 0
        while (len(self._received_command_packets) == 0) and (timeout <> 1):
            if (time() - timeout_start) > OSEKitsDefine.TIMEOUT:
                timeout = 1
                
        if timeout == 1:
            return "Serial Connection Timed out."
        
        result = self._received_command_packets.pop()
       
        if (result >> 6) == OSEKitsDefine.ACK_OK:
            return "success"
        else:
            return "acknowledge error"        
       
       
        return "success"    
       
   
   
    def sendControlPacket (self, controlValue):
        '''
        Function: sendControlPacket
       
        Description:
            Sends a control packet through the serial connection. Waits for acknowledgement packet.
        
        Returns:
            string  value from OSESerialComs._awaitAcknowledge
        '''
        if not self._output_com.isOpen():
            return "SERIAL ERROR: Serial object not open."
       
        controlpacket = 0x03 | (0x01 << 3) | (controlValue << 6)
        try:
            self._output_com.write(chr(controlpacket))
        except Exception, e:
            tkMessageBox.showerror("Serial Communication Error", "Unable to send control signal.\n Reconnect to micro-controlller.")
            return "SERIAL ERROR: write failed"

        return self._await_acknowledge()
